package cn.tedu.justone.pack01;

import java.util.concurrent.locks.StampedLock;

/**
 * ZZ：JustOne
 * VX：Andylau2020smile
 * DY：JustOne2018
 * ZT：《》
 */
public class Demo11_乐观读写锁StampedLock {

    //乐观读写锁StampedLock
    //乐观读写锁StampedLock
    //乐观读写锁StampedLock

    //基本介绍
    // 1,乐观读写锁StampedLock包含了悲观读写锁ReentrantReadWriteLock的所有方法，
    //   他的出现是要解决ReentrantReadWriteLock在读写分离时的线程饥饿问题。
    //   当ReentrantReadWriteLock想要获取写锁时，就必须没有任何其他读写锁存在才可以，
    //   在读操作很多、写操作很少的情况下，线程有可能就会遭遇饥饿问题，
    //   而StampedLock正是解决了这个问题，优化了读锁和写锁之间的相互转换。
    // 2,StampedLock通过引入了乐观读并不加锁，在读多写少的场景中性能
    //   会比ReentrantReadWriteLock要好！
    // 3,StampedLock实现了不仅多个读不互相阻塞，同时在读操作时不会阻塞写操作。
    //   为什么StampedLock能够达到这种效果呢，它的核心思想在于：在读的时候发现有写操作，
    //   应该通过重试的方式再去读多一次，而不应该阻塞写操作。
    //   这也是典型的无锁编程思想，所以StampedLock在"在 读线程非常多而写线程非常少"的
    //   场景下非常适用，同时还避免了写饥饿情况的发生。
    // 4,StampedLock控制锁有三种模式:
    //   乐观读 long stamp = stampedLock.tryOptimisticRead();
    //   悲观读 long stamp = stampedLock.readLock();
    //   写    long stamped = stampedLock.writeLock();
    //   如果线程拿到乐观锁就读和写不互斥，如果拿到悲观锁就读和写互斥。
    // 5,所有获取锁的方法，都会返回一个stamp戳印，这个stamp为0表示获取失败，其余均表示成功。
    //   所有释放锁的方法，都要传入一个stamp戳印，这个stamp必须和成功获取锁时返回的stamp一样。
    // 6,StampedLock是不可重入的锁，并且读性能比ReentrantReadWriteLock好，
    // 7,StampedLock支持读锁和写锁的互相转换。


    //测试乐观读写锁
    // 1,共有资源是num = 1;
    //   创建9条线程来读，创建1条线程来写。
    // 2,通过long stamp = stampedLock.tryOptimisticRead();来开始 乐观读，
    //   返回一个戳印stamp。
    //   读完之后通过 boolean b = stampedLock.validate(stamp);来判断戳印stamp
    //   是否有效(无变化则有效)，有效则读取成功。
    //   如果无效，则用stamp = stampedLock.readLock();悲观锁重新读一次。
    //

    //相关单词
    // stampedLock.tryOptimisticRead();
    // stamp [stæmp] 邮票，印; 章; 戳
    // Optimistic [ˌɑːptɪˈmɪstɪk] 乐观的

    static StampedLock stampedLock = new StampedLock();
    static int num = 1;

    public static void main(String[] args) {

        Thread t = new Thread("钢笔线程"){
            @Override
            public void run() {
                writeData();
            }
        };
        //t.start();

        for (int i = 0; i < 9; i++) {
            new Thread("线程" + i){
                @Override
                public void run() {
                    readData();
                }
            }.start();

            if(i==3){
                t.start();
            }
        }
    }

    //读数据
    public static void readData(){
        //开始乐观读锁，并返回stamp戳印值。该方法不会使用writeLock阻塞
        //乐观读锁因为实际上没有获取过锁，所以也就没有释放锁的过程，只是在操作后用于验证stamp戳印值有无变化。
        //如果执开始乐观读时该锁已被写占用，stamp戳印值就是0。
        long stamp = stampedLock.tryOptimisticRead();
        //读取数据
        System.out.println(Thread.currentThread().getName() + "正在读数据，num = " + num);
        //判断stamp戳印值是否有效。与原来相同则有效。。也可以改成while循环，直到拿到最新值
        if( !stampedLock.validate(stamp)){
            //stamp戳印值无效，说明读的过程中有人写过(从获取stamp戳印值，到判断stamp戳印值是否有效这期间)，
            //所以需要重新获取锁，读修改后的值
            try{
                //重新开始悲观读，并返回stamp戳印值
                stamp = stampedLock.readLock();
                System.out.println("数据有变化，"+Thread.currentThread().getName() + "正在重新读数据，num = " + num);
                mySleep(1000);
            }finally {
                stampedLock.unlockRead(stamp);
            }
        }
    }

    //写数据
    public static void writeData(){
        long stamped = -1;//stamp戳印值，定义到这里是方便finally调用
        try{
            System.out.println(Thread.currentThread().getName() + "在等待锁");
            //获取悲观写锁，并返回stamp戳印值
            stamped = stampedLock.writeLock();
            num++;
            System.out.println(Thread.currentThread().getName() + "正在写数据: num = " + num);
        }finally {
            stampedLock.unlockWrite(stamped);
            System.out.println(Thread.currentThread().getName() + "释放了wLock.lock()");
        }
    }

    //为了让代码更优雅，编写统一的休眠方法
    public static void mySleep(long m){
        try {
            Thread.sleep(m);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}
